﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Lab3
{
    public class Camera
    {
        private Matrix _view;
        private Matrix _projection;
        private float _leftrightRot;
        private float _updownRot;
        private float _rotationSpeed = 0.005f;
        private float _sensibility = 0.2f;
        private float _moveSpeed = 0.5f;
        private const float FieldOfView = MathHelper.PiOver4;
        private const float NearPlane = 0.01f;
        private const float FarPlane = 1000;
        private Vector3 _position;
        private Vector3 _target;
        private Vector3 _up;
        private readonly int _width;
        private readonly int _height;
        private MouseState _originalMouseState;

        public Camera(Viewport viewPort, Vector3 position, Vector3 up, float updownAngle = 0, float leftrightAngle = 0)
        {
            _position = position;
            _leftrightRot = leftrightAngle;
            _updownRot = updownAngle;
            _up = up;
            _width = viewPort.Width;
            _height = viewPort.Height;
            Mouse.SetPosition(viewPort.Width / 2, viewPort.Height / 2);
            _originalMouseState = Mouse.GetState();
            _projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView, viewPort.AspectRatio, NearPlane, FarPlane);
            UpdateViewMatrix();
        }

        public void Update(GameTime gt)
        {
            MouseState currentMouseState = Mouse.GetState();
            KeyboardState keyState = Keyboard.GetState();

            if (currentMouseState != _originalMouseState)
            {
                float xDifference = currentMouseState.X - _originalMouseState.X;
                float yDifference = currentMouseState.Y - _originalMouseState.Y;
                _leftrightRot -= _rotationSpeed * xDifference;
                _updownRot -= _rotationSpeed * yDifference;
                Mouse.SetPosition(_width / 2, _height / 2);
                UpdateViewMatrix();
                currentMouseState = _originalMouseState;
            }

            if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
                AddToCameraPosition(new Vector3(0, 0, -_sensibility));
            if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
                AddToCameraPosition(new Vector3(0, 0, _sensibility));
            if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
                AddToCameraPosition(new Vector3(_sensibility, 0, 0));
            if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
                AddToCameraPosition(new Vector3(-_sensibility, 0, 0));
            if (keyState.IsKeyDown(Keys.Space))
                AddToCameraPosition(new Vector3(0, _sensibility, 0));
            if (keyState.IsKeyDown(Keys.LeftShift))
                AddToCameraPosition(new Vector3(0,-_sensibility, 0));
        }

        private void UpdateViewMatrix()
        {
            Matrix cameraRotation = Matrix.CreateRotationX(_updownRot) * Matrix.CreateRotationY(_leftrightRot);
            Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
            Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
            Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
            _target = _position + cameraRotatedTarget;
            _up = Vector3.Transform(cameraOriginalUpVector, cameraRotation);
            _view = Matrix.CreateLookAt(_position, _target, _up);
        }

        private void AddToCameraPosition(Vector3 vectorToAdd)
        {
            Matrix cameraRotation = Matrix.CreateRotationX(_updownRot)*Matrix.CreateRotationY(_leftrightRot);
            Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
            _position += _moveSpeed*rotatedVector;
            UpdateViewMatrix();
        }

        public Matrix View
        {
            get { return _view; }
            set { _view = value; }
        }

        public Matrix Projection
        {
            get { return _projection; }
            set { _projection = value; }
        }

        public float RotationSpeed
        {
            get { return _rotationSpeed; }
            set { _rotationSpeed = value; }
        }

        public float Sensibility
        {
            get { return _sensibility; }
            set { _sensibility = value; }
        }

        public float MoveSpeed
        {
            get { return _moveSpeed; }
            set { _moveSpeed = value; }
        }

        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }
    }
}
